package com.zdpx.pxframework.log.aop;

import cn.hutool.core.util.StrUtil;
import com.zdpx.pxframework.log.annotations.OperateLog;
import com.zdpx.pxframework.log.dto.OperateLogModelDto;
import com.zdpx.pxframework.log.properties.LoggingProperties;
import com.zdpx.pxframework.log.service.OperateLogFrameworkService;
import com.zdpx.pxframework.log.util.LogUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotatedElementUtils;

import javax.annotation.Resource;
import java.util.Date;

/**
 * 拦截使用 @OperateLog 注解，如果满足条件，则生成操作日志。
 *
 * @Description: 满足如下任一条件，则会进行记录：
 * 1. 使用 @ApiOperation + 非 @GetMapping
 * 2. 使用 @OperateLog 注解
 * <p>
 * 但是，如果声明 @OperateLog 注解时，将 enable 属性设置为 false 时，强制不记录。
 * @Author: Cheng XiaoXiao
 * @Date: 2022/5/23 13:28
 */
@Aspect
@Slf4j
public class OperateLogAspect {

    @Resource
    LoggingProperties loggingProperties;

    @Resource
    private OperateLogFrameworkService operateLogFrameworkService;


    @Pointcut("execution(public * com.zdpx..*.controller.*Controller.*(..))")
    public void log() {
    }

    /**
     * 拦截标识了@ApiOperation的请求
     *
     * @param joinPoint 切入点
     * @return 返回值
     */
    @Around("log()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        OperateLog operateLog = AnnotatedElementUtils.findMergedAnnotation(((MethodSignature) joinPoint.getSignature()).getMethod(), OperateLog.class);
        ApiOperation apiOperation = AnnotatedElementUtils.findMergedAnnotation(((MethodSignature) joinPoint.getSignature()).getMethod(), ApiOperation.class);

        return aroundLog(joinPoint, operateLog, apiOperation);
    }

    /**
     * 进行日志处理
     *
     * @param joinPoint    切入点
     * @param operateLog   自定义的日志注解信息
     * @param apiOperation swagger中定义的注解信息
     * @return 返回值
     */
    private Object aroundLog(ProceedingJoinPoint joinPoint, OperateLog operateLog, ApiOperation apiOperation) throws Throwable {
        // 记录开始时间
        Date startTime = new Date();
        try {
            // 执行原有方法
            Object result = joinPoint.proceed();
            // 记录正常执行时的操作日志
            this.log0(joinPoint, operateLog, apiOperation, startTime, result, null);
            return result;
        } catch (Throwable exception) {
            this.log0(joinPoint, operateLog, apiOperation, startTime, null, exception);
            throw exception;
        }
    }

    /**
     * 进行日志处理
     *
     * @param joinPoint    切入点
     * @param operateLog   操作日志注解信息
     * @param apiOperation swagger 直接信息
     * @param startTime    开始时间
     * @param result       结果信息
     * @param exception    异常信息
     */
    private void log0(ProceedingJoinPoint joinPoint, OperateLog operateLog, ApiOperation apiOperation,
                      Date startTime, Object result, Throwable exception) {

        //如果日志无需记录,直接返回
        if (!isLogEnable(joinPoint, operateLog)) {
            return;
        }

        OperateLogModelDto operateLogModelDto = LogUtil.getOperateLogModelDto(joinPoint, operateLog, apiOperation, startTime, result, exception);

        //如果请求日志开启，通过日志打印出来
        if (loggingProperties.getHttpLog().isEnabled()) {
            printLog(operateLogModelDto);
        }

        //如果操作日志开启并且不为GET请求，保存日志信息
        if (loggingProperties.getOperatorLog().isEnabled() &&
                LogUtil.obtainFirstLogRequestMethod(LogUtil.obtainRequestMethod(joinPoint)) != null) {
            //保存日志信息
            operateLogFrameworkService.createOperateLogAsync(operateLogModelDto);
        }
    }

    /**
     * 进行请求日志的打印
     *
     * @param operateLogModelDto 日志对象
     */
    private void printLog(OperateLogModelDto operateLogModelDto) {
        //打印操作日志
        log.info("==========module:[{}]===[{}]-start==================", operateLogModelDto.getModuleName(), operateLogModelDto.getTitle());
        log.info("{} {}", operateLogModelDto.getRequestMethod(), operateLogModelDto.getRequestUri());
        log.info("params：{}", operateLogModelDto.getRequestArgs());
        if (!StrUtil.isBlank(operateLogModelDto.getException())) {
            log.info("exception：{}", operateLogModelDto.getException());
        }
        log.info("time：{} ms", operateLogModelDto.getUseTime());

        log.info("==========module:[{}]===[{}]-end====================", operateLogModelDto.getModuleName(), operateLogModelDto.getTitle());
    }


    /**
     * 判断是否需要记录日志信息
     *
     * @param joinPoint  切入点
     * @param operateLog 操作日志注解信息
     * @return 是否需要记录
     */
    private boolean isLogEnable(ProceedingJoinPoint joinPoint, OperateLog operateLog) {
        //日志开关关闭无需处理
        if (!loggingProperties.getOperatorLog().isEnabled() && !loggingProperties.getHttpLog().isEnabled()) {
            return false;
        }
        // 有 @OperateLog 注解的情况下
        if (operateLog != null) {
            return operateLog.enable();
        }
        // 没有 @ApiOperation 注解的情况下，只记录 POST、PUT、DELETE 的情况
        return true;
    }
}
